﻿using System;
using System.Collections.Generic;
using System.Data.EntityClient;
using System.IO;
using System.Linq;
using System.Text;
using COFE.Core;
using COFE.Core.Filter;
using COFE.Core.Profile;
using COFE.Core.Utils;
using NUnit.Framework;

namespace COFE.Data.UnitTest
{
    [TestFixture]
    public class TestCache
    {
        #region Constructor

        public static string TestVolumeKey = @"Temp";
        public static string TestVolumePath = @"C:\temp\TestCache";
        public static string TestPath = "A\\B";

        static TestCache()
        {

            COFEDBShared.Register(null, true);
            DummyDBPropertyProvider.EnableDummySecondaryPropertyProvider();

        }

        #endregion

        #region Methods

        public void CreateUser()
        {
            if (DBUserProfile.Exists("TestCache"))
                DBUserProfile.Remove("TestCache");
            IUserProfile user = DBUserProfile.Create("TestCache", "TestCache");
            if (COFEShared.VolumeRegistrar.LookupRegisterVolume(TestVolumeKey) == null)
                COFEShared.VolumeRegistrar.RegisterVolume(
                    COFEShared.VolumeRegistrar.ConstructVolume("FSI", TestVolumeKey, "path=" + TestVolumePath,
                    Core.Profile.VolumeAccessMode.All));

            if (COFEShared.VolumeRegistrar.LookupRegisterVolume("SZ") == null)
                COFEShared.VolumeRegistrar.RegisterVolume("SZ", "SZ",
                Core.Profile.VolumeAccessMode.All);

            user.RegisterVolume(TestVolumeKey);
            user.RegisterVolume("SZ");
        }

        public IUserProfile LoginUser()
        {
            var user = DBUserProfile.Login("TestCache", "TestCache");
            Assert.IsNotNull(user);
            return user;
        }

        /// <summary>
        /// Create the following structure.
        /// {Temp}
        /// |--TestCache
        ///       |-- EA
        ///           |---a.txt
        ///           |---a.zip        
        ///               |---aa.txt (create afterwards)
        ///               |---AAB (create afterwards)
        ///                   |--- aab.txt (create afterwards)        
        ///           |---B
        ///               |---ab.txt        
        ///           |---C
        ///                |--ac.txt
        ///           |---D (create afterwards)
        ///                |--ad.txt (create afterwards)
        /// </summary>
        public void CreateTemp()
        {
            Action<string> createZip = (path) =>
            {
                //byte[] EmptyZip = new byte[] { 0x50, 0x4B, 0x05, 0x06, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00 }; //PK
                string filePath = Path.Combine(TestVolumePath, path);

                using (var br = new BinaryReader(File.OpenRead("a.zip")))
                using (var bw = new BinaryWriter(File.Open(filePath, FileMode.OpenOrCreate)))
                {
                    byte[] buffer = new byte[1024];
                    br.Read(buffer, 0, buffer.Length);
                    bw.Write(buffer);
                }

            };

            Action<string> createFile = (path) =>
            {
                string filePath = Path.Combine(TestVolumePath, path);
                using (var sw = new StreamWriter(File.Open(filePath, FileMode.OpenOrCreate)))
                    sw.WriteLine(Path.GetFileName(path));
            };

            if (!Directory.Exists(TestVolumePath))
            {

                Directory.CreateDirectory(TestVolumePath);
                Directory.CreateDirectory(Path.Combine(TestVolumePath, "A"));
                Directory.CreateDirectory(Path.Combine(TestVolumePath, "A\\B"));
                Directory.CreateDirectory(Path.Combine(TestVolumePath, "A\\C"));

                createFile("A\\a.txt");
                createFile("A\\B\\ab.txt");
                createFile("A\\C\\ac.txt");
            }
            else
            {
                if (Directory.Exists(Path.Combine(TestVolumePath, "A\\D")))
                    Directory.Delete(Path.Combine(TestVolumePath, "A\\D"), true);
            }

            if (File.Exists(Path.Combine(TestVolumePath, "A\\a.zip")))
                File.Delete(Path.Combine(TestVolumePath, "A\\a.zip"));

            createZip("A\\a.zip");
        }

        public void ClearCache()
        {
            var removeGuid = StringUtils.GetDeterministicGuid(parsableVolumeKey);
            COFEDBShared.DBEntryManager.RemoveEntry(removeGuid);

            COFEDBShared.DBOperations.UpdateDatabase((context) =>
                {
                    Assert.IsNull(COFEDBShared.DBEntryManager.LookupEntry(context, removeGuid));
                    //using (var context = new COFEDB2())
                    //{
                    context.ExecuteStoreCommand("DELETE FROM \"Entries\"");
                    context.ExecuteStoreCommand("DELETE FROM \"EntryProperties\"");
                    context.ExecuteStoreCommand("DELETE FROM \"EntryProperties_Int32\"");
                    context.ExecuteStoreCommand("DELETE FROM \"EntryProperties_String\"");
                    context.ExecuteStoreCommand("DELETE FROM \"EntryProperties_Bool\"");
                    context.ExecuteStoreCommand("DELETE FROM \"EntryProperties_DateTime\"");
                    context.ExecuteStoreCommand("DELETE FROM \"EntryProperties_Guid\"");
                    //}
                });
        }

        [Test]
        public void Cache_Directory_And_Its_Parent()
        {
            CreateTemp();
            CreateUser();
            ClearCache();
            IUserProfile user = LoginUser();

            //Add to database, if it's not already.
            string parsablePath = COFEPath.Combine(parsableVolumeKey, TestPath);
            IDirectoryInfo foundDir = user.TryParsePath(parsablePath, EntryConstructionFlags.Default) as IDirectoryInfo;
            Assert.IsNotNull(foundDir);
            Assert.AreEqual(parsablePath, foundDir.ParseName);

            //Check if it's parent is in database.
            Guid parentGuid = (foundDir as IInternalDirectoryInfoExA).ParentId;
            IDirectoryInfo parent = user.TryParseGuid(parentGuid, EntryConstructionFlags.Default) as IDirectoryInfo;
            Assert.IsNotNull(parent);
            Assert.AreEqual(foundDir.Parent.ParseName, parent.ParseName);

            //If the entries is in database, then using guid should able to find it.
            IDirectoryInfo foundDir2 = user.TryParseGuid(foundDir.GetId(), EntryConstructionFlags.Default) as IDirectoryInfo;
            Assert.IsNotNull(foundDir2);
            Assert.AreEqual(parsablePath, foundDir2.ParseName);

            Assert.AreEqual(1, foundDir2.GetFileSystemInfos("*").Length);
        }

        [Test]
        public void Cache_Listing()
        {
            CreateTemp();
            CreateUser();
            ClearCache();
            IUserProfile user = LoginUser();

            string parsablePath = COFEPath.Combine(parsableVolumeKey, "A");
            var foundDir = user.TryParsePath(parsablePath, EntryConstructionFlags.Default) as IInternalDirectoryInfoExA;
            Assert.IsNotNull(foundDir);
            Assert.AreEqual(parsablePath, foundDir.ParseName);

            COFEDBShared.DBOperations.ReadDatabase(
                (context) => COFEDBShared.DBEntryManager.LookupEntry(context, foundDir.GetId()) as FolderEntry,
                (context, foundDirEntry) =>
                {
                    Assert.IsNotNull(foundDirEntry);
                    Assert.AreEqual(COFEDBShared.DTMin, foundDirEntry.LastListTime);
                });

            IFileSystemInfo[] subContents = null;

            var timerUncached = Benchmark(4, "- ListNoCache", () =>
            {
                subContents = foundDir.EnumerateFSIs<IFileSystemInfo>(
                    new IEntryFilter[] {  },
                    new IOptionFilter[] { new SearchOptionOptionFilter(SearchOptionEx.TopDirectoryOnly) },
                    EntryConstructionFlags.Default).ToArray();
            });

            COFEDBShared.DBOperations.ReadDatabase(
                (context) => COFEDBShared.DBEntryManager.LookupEntry(context, foundDir.GetId()) as FolderEntry,
                (context, foundDirEntry) =>
                {

                    Assert.AreNotEqual(COFEDBShared.DTMin, foundDirEntry.LastListTime);
                    Assert.AreEqual(4, subContents.Length); //a.txt, a.zip, B and C.           
                    Assert.AreEqual(4, COFEDBShared.DBEntryManager.
                        LookupEntry(context, foundDir.GetId()).Children.Count);

                });

            //Load from cache.

            IFileSystemInfo[] subContents2 = null;

            var timerCached = Benchmark(4, "- ListCached", () =>
            {
                subContents2 = foundDir.EnumerateFSIs<IFileSystemInfo>(
                    new IEntryFilter[] {  },
                    new IOptionFilter[] { new SearchOptionOptionFilter(SearchOptionEx.TopDirectoryOnly) },
                    EntryConstructionFlags.Default).ToArray();
                Assert.AreEqual(4, subContents2.Length); //a.txt, a.zip, B and C.           
            });

            Assert.Less(timerCached, timerUncached);


        }

        public void Cache_Zip_Listing()
        {
            CreateTemp();
            CreateUser();
            ClearCache();
            IUserProfile user = LoginUser();

            string parsablePath = COFEPath.Combine(parsableVolumeKey, "A\\a.zip");

            IInternalDirectoryInfoExA foundZip = null;
            Benchmark(4, "- Parse_Zip", () =>
                {
                    foundZip = user.TryParsePath(parsablePath, EntryConstructionFlags.Default) as IInternalDirectoryInfoExA;
                    Console.WriteLine(foundZip.GetProperty(COFE.Data.UnitTest.DummyDBPropertyProvider.DummyProperties.DummyInt).Value);
                    Assert.IsNotNull(foundZip);
                    Assert.AreEqual(parsablePath, foundZip.ParseName);
                    Assert.IsNotInstanceOf<DBCachePropertyProvider>(foundZip.PrimaryPropertyProvider);
                });


            IInternalDirectoryInfoExA foundZipInDB = null;
            Benchmark(4, "- Parse_Zip_From_DB", () =>
            {
                foundZipInDB = user.TryParsePath(parsablePath, EntryConstructionFlags.Default) as IInternalDirectoryInfoExA;
                Assert.AreEqual(COFEDBShared.DTMin, foundZipInDB.GetProperty(DatabaseProperties.LastListTime).ValueAsUtcDateTime);
                Assert.IsNotInstanceOf<DBCachePropertyProvider>(foundZip.PrimaryPropertyProvider);
                //var subEntries = foundZipInDB.GetFileSystemInfos();
                //Assert.AreEqual(2, subEntries.Length); //aa and aa.txt 
            });

            Benchmark(4, "- Parse_Txt_In_Zip", () =>
            {
                var foundTxt = user.TryParsePath(COFEPath.Combine(parsableVolumeKey, "A\\a.zip\\aa.txt"),
                    EntryConstructionFlags.Default) as IInternalFileSystemInfoExA;
                Assert.IsNotNull(foundTxt);
                Assert.AreEqual("{Temp}\\A\\a.zip\\aa.txt", foundTxt.ParseName);
                Assert.IsInstanceOf<DBCachePropertyProvider>(foundTxt.PrimaryPropertyProvider);
            });

            Benchmark(4, "- Parse_Folder_In_Zip", () =>
            {
                var foundZipFolder = user.TryParsePath(COFEPath.Combine(parsableVolumeKey, "A\\a.zip\\aab"),
                    EntryConstructionFlags.Default) as IInternalFileSystemInfoExA;
                Assert.IsNotNull(foundZipFolder);
                Assert.AreEqual("{Temp}\\A\\a.zip\\AAB", foundZipFolder.ParseName);
                Assert.IsInstanceOf<DBCachePropertyProvider>(foundZipFolder.PrimaryPropertyProvider);
            });


            Benchmark(4, "- Parse_Entry_Using_Id", () =>
                {
                    COFEDBShared.DBOperations.ReadDatabase(
                        (context) => COFEDBShared.DBEntryManager.LookupEntry(context, foundZip.GetId()),
                        (context, foundEntry) =>
                        {
                            Assert.IsNotNull(foundEntry);
                            Assert.IsInstanceOf(typeof(FileEntry), foundEntry);
                            FileEntry foundFileEntry = foundEntry as FileEntry;
                            foundFileEntry.EntryProperties.Load();
                        });
                });




            IFileSystemInfo[] subEntries1 = null, subEntries2 = null;
            Benchmark(4, "- List_From_SevenZip", () =>
                {
                    //List once from SevenZip
                    subEntries1 = foundZipInDB.GetFileSystemInfos("*", SearchOptionEx.AllDirectories);
                    Assert.AreEqual(3, subEntries1.Length); //aa and aa.txt and aab.txt
                    Assert.AreNotEqual(COFEDBShared.DTMin, foundZipInDB.GetProperty(DatabaseProperties.LastListTime).ValueAsUtcDateTime);
                });

            Benchmark(4, "- List_From_Database", () =>
                {
                    //List again from Database.
                    subEntries2 = foundZipInDB.GetFileSystemInfos("*", SearchOptionEx.AllDirectories);
                    Assert.AreEqual(3, subEntries2.Length); //aa and aa.txt and aab.txt
                    Assert.AreNotEqual(COFEDBShared.DTMin, foundZipInDB.GetProperty(DatabaseProperties.LastListTime).ValueAsUtcDateTime);
                });
        }

        [Test]
        public void Multi_Level_Listing()
        {
            CreateTemp();
            CreateUser();
            ClearCache();
            IUserProfile user = LoginUser();

            var root = user.TryParsePath(parsableVolumeKey, EntryConstructionFlags.Default) as IInternalDirectoryInfoExA;
            Assert.IsNotNull(root);
            Assert.AreEqual(parsableVolumeKey, root.ParseName);

            IFileSystemInfo[] subContents = null;
            var timerUncached = Benchmark(4, "- ListNoCache", () =>
            {
                subContents = root.EnumerateFSIs<IFileSystemInfo>(new IEntryFilter[] {  },
                    new IOptionFilter[] { new SearchOptionOptionFilter(SearchOptionEx.AllDirectoriesAndFileBasedFS) },
                    EntryConstructionFlags.Default).ToArray();
            });
            Assert.AreEqual(10, subContents.Length); //EA, B and C.           

            IFileSystemInfo[] subContents2 = null;
            var timerCached = Benchmark(4, "- ListCached", () =>
            {
                subContents2 = root.EnumerateFSIs<IFileSystemInfo>(new IEntryFilter[] {  },
                 new IOptionFilter[] { new SearchOptionOptionFilter(SearchOptionEx.AllDirectoriesAndFileBasedFS) },
                 EntryConstructionFlags.Default).ToArray();

            });
            Assert.AreEqual(10, subContents2.Length); //EA, B and C.           

            IFileSystemInfo[] subContents3 = null;
            var timerCached3 = Benchmark(4, "- ListTxtFile", () =>
            {
                subContents3 = root.EnumerateFSIs<IFileSystemInfo>(new IEntryFilter[] { new FileTypeEntryFilter("txt") },
                 new IOptionFilter[] { new SearchOptionOptionFilter(SearchOptionEx.AllDirectoriesAndFileBasedFS) },
                 EntryConstructionFlags.Default).ToArray();

            });
            Assert.AreEqual(5, subContents3.Length); //a.txt, ab.txt and ac.txt, exclude a.zip

        }

        [Test]
        public void Listing_Refresh_When_FileSystem_Changed()
        {
            Cache_Listing();
            IUserProfile user = LoginUser();

            //Create a Directory, EA\D
            Benchmark(4, "- CreateDirectory", () =>
            {
                COFEShared.CurrentUserProfile.Directory.CreateDirectory("A\\D");
            });

            Benchmark(4, "- CreateFile", () =>
                {
                    using (var sw = new StreamWriter(COFEShared.CurrentUserProfile.File.Create("A\\D\\ad.txt")))
                        sw.WriteLine("ad.txt");
                });

            Benchmark(4, "- CreateFileInZip", () =>
            {
                using (var sw = new StreamWriter(COFEShared.CurrentUserProfile.File.Create("A\\a.zip\\aa.txt")))
                    sw.WriteLine("aa.txt");
            });

            string parsablePath = COFEPath.Combine(parsableVolumeKey, "A");
            var foundDir = user.TryParsePath(parsablePath, EntryConstructionFlags.Default) as IInternalDirectoryInfoExA;

            //Assert.AreEqual(COFEDBShared.DTMin, foundDirEntry.LastListTime);
            //B, C and D
            COFEDBShared.DBOperations.ReadDatabase((context) =>
                {
                    Assert.AreEqual(5, 
                        COFEDBShared.DBEntryManager.LookupEntry(context, foundDir.GetId()).Children.Count);
                });

        }

        public static string Indent(int count)
        {
            StringBuilder sb = new StringBuilder();
            for (var i = 0; i < count; i++)
                sb.Append(" ");
            return sb.ToString();
        }

        TimeSpan Benchmark(int indent, string name, Action action)
        {
            if (indent == 0)
                Console.WriteLine(Indent(indent) + name);
            else Console.Write(Indent(indent) + name + " - ");

            DateTime startTime = DateTime.UtcNow;
            action.Invoke();
            DateTime endTime = DateTime.UtcNow;
            TimeSpan timeUsed = endTime.Subtract(startTime);
            if (indent == 0)
                Console.WriteLine(Indent(indent) + "Total time spent - " + timeUsed.TotalSeconds.ToString() + "s");
            else Console.WriteLine(timeUsed.TotalSeconds.ToString() + "s");
            return timeUsed;

        }


        public void Test()
        {
            Benchmark(0, "Cache_Directory_And_Its_Parent", () => { Cache_Directory_And_Its_Parent(); });
            Console.WriteLine();

            Benchmark(0, "Cache_Listing", () => { Cache_Listing(); });
            Console.WriteLine();

            Benchmark(0, "Cache_Zip_Listing", () => { Cache_Zip_Listing(); });
            Console.WriteLine();

            Benchmark(0, "Listing_Refresh_When_FileSystem_Changed", () => { Listing_Refresh_When_FileSystem_Changed(); });
            Console.WriteLine();

            Benchmark(0, "Multi_Level_Listing", () => { Multi_Level_Listing(); });
            Console.WriteLine();

        }
        #endregion

        #region Data

        #endregion

        #region Public Properties

        public string parsableVolumeKey { get { return "{" + TestVolumeKey + "}"; } }

        #endregion
    }
}
